﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Infrastructure;
using Infrastructure.Listener;
using SubsDownloader.Core.AppSettings;
using SubsDownloader.Core.Entities;
using SubsDownloader.Core.EventArgs;
using SubsDownloader.Core.Interfaces;
using SubsDownloader.Core.Providers;

namespace SubsDownloader.Core
{
    public class DownloaderManager
    {
        #region Data Members

        /// <summary>
        /// contains the folders which the manager is monitoring
        /// </summary>
        private readonly List<FolderListener> _monitoredFolders;

        /// <summary>
        /// manage the search subsc threads
        /// </summary>
        private readonly ThreadsManager<TitleFileInfo> _downloadManager;

        /// <summary>
        /// holds the sub titles downloaders sorted by priority
        /// </summary>
        private readonly SortedList<int, Type> _subsDownloaders;

        private bool _keepAlive;

        private Dictionary<TitleFileInfo, ISubsDownloader> _titlesDownloader;
        #endregion

        public bool Running { get { return _keepAlive; } }

        /// <summary>
        /// fired when new file has been recived
        /// </summary>
        public event Action<object, TitleFileInfo> FileRecived;

        public event EventHandler<NoMatchTitleFoundEventArgs> NoTitleMatchFound;

        public event EventHandler<NoMatchSubtitleFoundEventArgs> NoSubtitleMatchFound;

        /// <summary>
        /// init the manager with the folder you wish to monitor.
        /// note that the manager montior max 5 directories depth
        /// </summary>
        public DownloaderManager ()
        {
            _keepAlive = false;
            SettingsManager.Instane.Reset += new EventHandler( Instane_Reset );
            _titlesDownloader = new Dictionary<TitleFileInfo, ISubsDownloader>();
            _subsDownloaders = new SortedList<int, Type>();
            _subsDownloaders.Add( 1, typeof( SubsCenterProvider ) );
            _subsDownloaders.Add( 10, typeof( SratimProvider ) );

            _monitoredFolders = new List<FolderListener>();

            var subtitlesSettings = SettingsManager.Instane.GetSettings<SubtitlesSettings>();
            var folderScanerSettings = SettingsManager.Instane.GetSettings<FolderScanerSettings>();

            _downloadManager = new ThreadsManager<TitleFileInfo>( 1000, subtitlesSettings.WorkerThreads );
            _downloadManager.HandleWorkEvent += DownloadManagerHandleWorkEvent;
            _downloadManager.StartAsync( true );

            if (folderScanerSettings.FolderToMonitor != null)
            {
                foreach (string folder in folderScanerSettings.FolderToMonitor)
                {
                    var folderListener = new FolderListener( folder, "*.*", 3, folderScanerSettings.MonitorInterval );
                    folderListener.IgnoreReportedFiles = true;
                    folderListener.FileReceived += FolderListenerFileReceived;

                    _monitoredFolders.Add( folderListener );
                }
            }
        }


        /// <summary>
        /// Start to monitor the folders
        /// </summary>
        public void StartAsync ()
        {
            if (_monitoredFolders.Count == 0)
                throw new Exception( "couldnt find and folder to monitor." );

            foreach (var monitoredFolder in _monitoredFolders)
            {
                monitoredFolder.StartAsync();
            }

            _keepAlive = true;
        }

        /// <summary>
        /// stop monitoring folders and download subs
        /// </summary>
        public void Stop ()
        {
            _keepAlive = false;
            foreach (var monitoredFolder in _monitoredFolders)
            {
                monitoredFolder.Stop();
            }
        }

        /// <summary>
        /// Add new montior folder.
        /// note that folder cannot be monitored twice
        /// </summary>
        /// <param name="path"></param>        
        public void MonitorFolder ( string path )
        {
            var folderScanerSettings = SettingsManager.Instane.GetSettings<FolderScanerSettings>();

            if (!_monitoredFolders.Any( mf => mf.PickupFolder == path ))
            {
                folderScanerSettings.FolderToMonitor.Add( path );
                SettingsManager.Instane.SaveSettings();

                var folderListener = new FolderListener( path, "*.*", 3, folderScanerSettings.MonitorInterval );
                folderListener.IgnoreReportedFiles = true;
                folderListener.FileReceived += FolderListenerFileReceived;
                _monitoredFolders.Add( folderListener );
                if (_keepAlive)
                {
                    folderListener.StartAsync();
                }
            }
        }

        /// <summary>
        /// Download Subtilte from file name
        /// </summary>
        /// <param name="filePath">Title file path</param>
        public void DownloadSubtitles ( string filePath )
        {
            if (!File.Exists( filePath ))
                throw new FileNotFoundException( string.Format( "Cannot find file {0}", filePath ) );

            QueueDownload( filePath );
        }

        public void DownloadSubtitles ( TitleFileInfo titleFileInfo, bool overrideExisting = false )
        {
            titleFileInfo.Status = eStatus.Process;
            if (!titleFileInfo.HasSubs || overrideExisting)
            {
                titleFileInfo.HasSubs = false;
                _downloadManager.InsertWorkItem( titleFileInfo, new TimeSpan( 0, 0, 0, 2 ) );
            }
        }

        public void CancelDownload ( TitleFileInfo titleFileInfo )
        {
            titleFileInfo.Status = eStatus.Canceled;
            _titlesDownloader[titleFileInfo].CancelDownload();
        }

        public void DownloadSubtitles ( TitleFileInfo titleFileInfo, TitleResult titleResult )
        {
            titleFileInfo.Status = eStatus.Process;
            _titlesDownloader[titleFileInfo].SelectTitle( titleResult );
        }

        public void DownloadSubtitles ( TitleFileInfo titleFileInfo, SubtitleResult subtitleResult )
        {
            ThreadPool.QueueUserWorkItem( s => _titlesDownloader[titleFileInfo].SelectSubtitles( subtitleResult ) );
        }

        #region Private Methods

        private void Instane_Reset ( object sender, System.EventArgs e )
        {
            foreach (var monitoredFolder in _monitoredFolders)
            {
                monitoredFolder.Stop();
            }
        }
   
        /// <summary>
        /// handel the file recived event by the folder monitors
        /// </summary>
        /// <param name="arg1"></param>
        /// <param name="arg2"></param>
        private void FolderListenerFileReceived ( FolderListener arg1, ReceivedFileInfo arg2 )
        {
            arg2.DeleteOnComplete = false;
            QueueDownload( arg2.FullName );
        }

        private void QueueDownload ( string fileName )
        {
            if (fileName.ToLower().Contains( "sample" ) || fileName.ToLower().Contains( "trailer" )) return;
            var subtitlesSettings = SettingsManager.Instane.GetSettings<SubtitlesSettings>();
            var folderScanerSettings = SettingsManager.Instane.GetSettings<FolderScanerSettings>();

            //check that the file is supported extention

            var fileExtention = Path.GetExtension( fileName );
            var supported = folderScanerSettings.MediaTypes.Any( s => s == fileExtention );
            if (!supported) return;

            var titleFileInfo = new TitleFileInfo( fileName );
            if (subtitlesSettings.PrefreFolderName && !titleFileInfo.IsSeries)
            {
                titleFileInfo = titleFileInfo.TitleFolderInfo;
            }
            var ignoredMedia = folderScanerSettings.IgnoredMedia;
            var ignoredFile = ignoredMedia.Contains( titleFileInfo.TitleName ) || ignoredMedia.Contains( titleFileInfo.ToString() ) ||
                              ignoredMedia.Contains( titleFileInfo.ToString( "S" ) );

            if (!ignoredFile)
            {
                if (( folderScanerSettings.LoadOnlyFilesWithoutSubtitles && !titleFileInfo.HasSubs ) ||
                    !folderScanerSettings.LoadOnlyFilesWithoutSubtitles)
                {
                    InvokeFileRecived( titleFileInfo );
                    if (!titleFileInfo.HasSubs)
                    {
                        _downloadManager.InsertWorkItem( titleFileInfo, new TimeSpan( 0, 0, 0, 2 ) );
                    }
                }
            }
        }


        /// <summary>
        /// handel the items that need subs
        /// </summary>
        /// <param name="obj"></param>
        private void DownloadManagerHandleWorkEvent ( ThreadsManager<TitleFileInfo>.Handle obj )
        {
            var folderScanerSettings = SettingsManager.Instane.GetSettings<FolderScanerSettings>();

            obj.HandleItem = DownloadSubtitlesInternal( obj.Item );
            //if failed to download the subs try again later
            if (!obj.HandleItem)
            {
                _downloadManager.InsertWorkItem( obj.Item, folderScanerSettings.ReSearchSubtitlesInterval );
            }
        }

        private bool DownloadSubtitlesInternal ( TitleFileInfo titleFileInfo )
        {
            bool success = false;
            foreach (var subsDownloader in _subsDownloaders)
            {
                //download the subs only if file has no subs already
                if (!titleFileInfo.HasSubs)
                {
                    var isubsDownloader = (ISubsDownloader)Activator.CreateInstance( subsDownloader.Value );
                    isubsDownloader.NoSubtitleMachFound += IsubsDownloaderNoSubtitleMachFound;
                    isubsDownloader.NoTitleMachFound += IsubsDownloaderNoTitleMachFound;
                    titleFileInfo.Status = eStatus.Process;
                    success = isubsDownloader.DownloadSubtitle( titleFileInfo );
                }

                //stop searching for subs
                if (success)
                    break;

            }
            titleFileInfo.Status = success ? eStatus.Sucess : eStatus.Fail;
            return success;
        }

        private void IsubsDownloaderNoTitleMachFound ( object sender, NoMatchTitleFoundEventArgs e )
        {
            e.TitleFileInfo.Status = eStatus.Pause;
            if (!_titlesDownloader.ContainsKey( e.TitleFileInfo ))
            {
                _titlesDownloader.Add( e.TitleFileInfo, (ISubsDownloader)sender );
            }
            else
            {
                _titlesDownloader[e.TitleFileInfo] = (ISubsDownloader)sender;
            }
            if (NoTitleMatchFound != null)
                NoTitleMatchFound( sender, e );
        }

        private void IsubsDownloaderNoSubtitleMachFound ( object sender, NoMatchSubtitleFoundEventArgs e )
        {
            e.TitleFileInfo.Status = eStatus.Pause;
            if (!_titlesDownloader.ContainsKey( e.TitleFileInfo ))
            {
                _titlesDownloader.Add( e.TitleFileInfo, (ISubsDownloader)sender );
            }
            else
            {
                _titlesDownloader[e.TitleFileInfo] = (ISubsDownloader)sender;
            }
            if (NoSubtitleMatchFound != null)
                NoSubtitleMatchFound( sender, e );
        }

        private void InvokeFileRecived ( TitleFileInfo file )
        {
            Action<object, TitleFileInfo> handler = FileRecived;
            if (handler != null) handler( this, file );
        }

        #endregion
    }
}
